using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Verse;

using Verse.Sound;
using Verse.AI;


namespace RimWorld{

public enum PlantLifeStage : byte
{
	Sowing,
	Growing,
	Mature,
}

[StaticConstructorOnStartup]
public class Plant : Thing
{
	//Working vars
	protected float 					growthInt = 0.05f; //Start in growing phase by default, set to 0 if sowing
	protected int						ageInt = 0;
	protected int						unlitTicks = 0;
	protected int						madeLeaflessTick = -99999;
	public bool							sown = false;

	//Working vars - cache
	private string						cachedLabelMouseover = null;

	//Fast vars
	private static Color32[]			workingColors = new Color32[4];

	//Constants and content
	public const float					BaseGrowthPercent = 0.05f;
	private const float					DyingDamagePerTick = 1f/200f;
	private const float					GridPosRandomnessFactor = 0.30f;
	private const int					TicksWithoutLightBeforeStartDying = (int)(GenDate.TicksPerDay * 7.5f);
	private const int					LeaflessMinRecoveryTicks = 60000;	//Minimum time to not show leafless after being made leafless
    public const float					MinGrowthTemperature = 0;			//Min temperature at which plant can grow or reproduce
    public const float					MinOptimalGrowthTemperature = 10f;
    public const float					MaxOptimalGrowthTemperature = 42f;
    public const float					MaxGrowthTemperature = 58;			//Max temperature at which plant can grow or reproduce
	public const float					MaxLeaflessTemperature = -2;
	private const float					MinLeaflessTemperature = -10;
	private const float					MinAnimalEatPlantsTemperature = 0;
	public const float					SeedShootMinGrowthPercent = 0.6f;
	private static Graphic				GraphicSowing = GraphicDatabase.Get<Graphic_Single>("Things/Plant/Plant_Sowing", ShaderDatabase.Cutout, Vector2.one, Color.white);

	//Properties
	public virtual float Growth
	{
		get{ return growthInt; }
		set
		{
			growthInt = value;
			cachedLabelMouseover = null;
		}
	}
	public virtual int Age
	{
		get{ return ageInt; }
		set
		{
			ageInt = value;
			cachedLabelMouseover = null;
		}
	}
	public virtual bool HarvestableNow
	{
		get
		{
			return def.plant.Harvestable && growthInt > def.plant.harvestMinGrowth;
		}
	}
	public override bool IngestibleNow
	{
		get
		{
			//Trees are always edible
			// This allows alphabeavers completely destroy the tree ecosystem
			if( def.plant.IsTree )
				return true;

			if( growthInt < def.plant.harvestMinGrowth )
				return false;

			if( LeaflessNow )
				return false;

			if( Spawned && Position.GetSnowDepth(Map) > def.hideAtSnowDepth )
				return false;

			return true;
		}
	}
	public virtual bool Dying
	{
		get
		{
			if( def.plant.LimitedLifespan && ageInt > def.plant.LifespanTicks )
				return true;

			if( unlitTicks > TicksWithoutLightBeforeStartDying )
				return true;

			return false;
		}
	}
	protected virtual bool Resting
	{
		get
		{
			return GenLocalDate.DayPercent(this) < 0.25f || GenLocalDate.DayPercent(this) > 0.8f;
		}
	}
	public virtual float GrowthRate
	{
		get
		{
			return GrowthRateFactor_Fertility * GrowthRateFactor_Temperature * GrowthRateFactor_Light;
		}
	}
	protected float GrowthPerTick
	{
		get
		{
			if( LifeStage != PlantLifeStage.Growing || Resting )
				return 0;

			float baseRate = 1 / (GenDate.TicksPerDay * def.plant.growDays);
			return baseRate * GrowthRate;
		}
	}
	public float GrowthRateFactor_Fertility
	{
		get
		{
			return (Map.fertilityGrid.FertilityAt(Position) * def.plant.fertilitySensitivity)
				 + (1f-def.plant.fertilitySensitivity);
		}
	}
	public float GrowthRateFactor_Light
	{
		get
		{
			float raw = Mathf.InverseLerp( def.plant.growMinGlow, def.plant.growOptimalGlow, Map.glowGrid.GameGlowAt(Position));
			return Mathf.Clamp01(raw);
		}
	}
	public float GrowthRateFactor_Temperature
    {
        get
        {
            float cellTemp;
			if( !GenTemperature.TryGetTemperatureForCell(Position, Map, out cellTemp) )
				return 1;

            if (cellTemp < MinOptimalGrowthTemperature)
				return Mathf.InverseLerp( MinGrowthTemperature, MinOptimalGrowthTemperature, cellTemp );
            else if (cellTemp > MaxOptimalGrowthTemperature)
				return Mathf.InverseLerp( MaxGrowthTemperature, MaxOptimalGrowthTemperature, cellTemp );
			else
				return 1;
        }
    }
	protected int TicksUntilFullyGrown
	{
		get
		{
			if( growthInt > 0.9999f )
				return 0;

			return (int)((1f-growthInt) / GrowthPerTick);
		}
	}
	protected string GrowthPercentString
	{
		get
		{
			return (growthInt + 0.0001f).ToStringPercent();
		}
	}
	public override string LabelMouseover
	{
		get
		{
			if( cachedLabelMouseover == null )
			{
				StringBuilder sb = new StringBuilder();
				sb.Append(def.LabelCap);
				sb.Append(" (" + "PercentGrowth".Translate(GrowthPercentString));
			
				if( Dying )
					sb.Append(", " + "DyingLower".Translate() );
			
				sb.Append(")");
				cachedLabelMouseover = sb.ToString();
			}

			return cachedLabelMouseover;
		}
	}
	protected virtual bool HasEnoughLightToGrow
	{
		get
		{
			return GrowthRateFactor_Light > 0.001f;
		}
	}
	public PlantLifeStage LifeStage
	{
		get
		{
			if( growthInt < 0.001f )
				return PlantLifeStage.Sowing;

			if( growthInt > 0.999f )
				return PlantLifeStage.Mature;

			return PlantLifeStage.Growing;
		}
	}
	public override Graphic Graphic
	{
		get
		{
			if( LifeStage == PlantLifeStage.Sowing )
				return GraphicSowing;

			if( def.plant.leaflessGraphic != null && LeaflessNow )
				return def.plant.leaflessGraphic;

			return base.Graphic;
		}
	}
	public bool LeaflessNow
	{
		get
		{
			if( Find.TickManager.TicksGame - madeLeaflessTick < LeaflessMinRecoveryTicks )
				return true;
			else
				return false;
		}
	}
	protected virtual float LeaflessTemperatureThresh
	{
		get
		{
			float diff = MaxLeaflessTemperature - MinLeaflessTemperature;
			float leaflessThresh = ((this.HashOffset() * 0.01f) % diff) - diff + MaxLeaflessTemperature;

			return leaflessThresh;
		}
	}
	public bool IsCrop
	{
		get
		{
			if( !def.plant.Sowable )
				return false;

			if( !Spawned )
			{
				Log.Warning("Can't determine if crop when unspawned.");
				return false;
			}

			return def == WorkGiver_Grower.CalculateWantedPlantDef(Position, Map);
		}
	}



	//temp for debug
	public override void Destroy(DestroyMode mode = DestroyMode.Vanish)
	{
		base.Destroy(mode);
	}

	public override void SpawnSetup(Map map)
	{
		base.SpawnSetup(map);

		//Don't call during init because indoor warm plants will all go leafless if it's cold outside
		if( Current.ProgramState == ProgramState.Playing )
			CheckTemperatureMakeLeafless();
	}
	
	public override void ExposeData()
	{
		base.ExposeData();
		
		Scribe_Values.LookValue(ref growthInt, 	"growth");
		Scribe_Values.LookValue(ref ageInt, 		"age", 0);
		Scribe_Values.LookValue(ref unlitTicks,	"unlitTicks", 0 );
		Scribe_Values.LookValue(ref sown,		"sown", false);
	}

	public override void PostMapInit()
	{
		CheckTemperatureMakeLeafless();
	}

	protected override void IngestedCalculateAmounts(Pawn ingester, float nutritionWanted, out int numTaken, out float nutritionIngested)
	{
		//Determine nutrition to gain
		//Take growth into account only for sown plants
		//This controls exploiting sowable plants for animal feed
		float nut = def.ingestible.nutrition;
		if( def.plant.Sowable )
			nut *= growthInt;
		else
			nut *= Mathf.Lerp(0.5f, 1, growthInt);

		//Affect this thing
		if( def.plant.harvestDestroys )
			numTaken = 1;
		else
		{
			growthInt -= 0.30f;

			if( growthInt < 0.08f )
				growthInt = 0.08f;

			if( Spawned )
				Map.mapDrawer.MapMeshDirty(Position, MapMeshFlag.Things);

			numTaken = 0;
		}

		nutritionIngested = nut;
	}

	public virtual void PlantCollected()
	{
		if( def.plant.harvestDestroys )
			Destroy();
		else
		{
			growthInt = 0.08f;
			Map.mapDrawer.MapMeshDirty(Position, MapMeshFlag.Things);
		}
	}
	
	protected virtual void CheckTemperatureMakeLeafless()
	{
		if( Position.GetTemperature(Map) < LeaflessTemperatureThresh )
			MakeLeafless(causedByCold: true);
	}

	public virtual void MakeLeafless(bool causedByCold = false)
	{
		bool changed = !LeaflessNow;
		var map = Map; // before applying damage

		madeLeaflessTick = Find.TickManager.TicksGame;

		if( def.plant.dieIfLeafless )
		{
			if( causedByCold )
			{
				if( IsCrop && MessagesRepeatAvoider.MessageShowAllowed("MessagePlantDiedOfCold-"+def.defName, 240f) )
					Messages.Message( "MessagePlantDiedOfCold".Translate(Label).CapitalizeFirst(), new TargetInfo(Position, map), MessageSound.Negative );
			}

			TakeDamage(new DamageInfo(DamageDefOf.Rotting, 99999));	
		}

		if( changed )
			map.mapDrawer.MapMeshDirty( Position, MapMeshFlag.Things );
	}

	public override void TickLong()
	{
		CheckTemperatureMakeLeafless();

		if( Destroyed )
			return;

		if( GenPlant.GrowthSeasonNow(Position, Map) )
		{
			bool hasLight = HasEnoughLightToGrow;

			//Record light starvation
			if( !hasLight )
				unlitTicks += GenTicks.TickLongInterval;
			else
				unlitTicks = 0;
	
			//Grow
			float prevGrowth = growthInt;
			bool wasMature = LifeStage == PlantLifeStage.Mature;
			growthInt += GrowthPerTick * GenTicks.TickLongInterval;

			if( growthInt > 1f )
				growthInt = 1f;

			//Regenerate layers
			if( (!wasMature && LifeStage == PlantLifeStage.Mature)
				|| (int)(prevGrowth * 10f) != (int)(growthInt * 10f) )
			{
				if( CurrentlyCultivated() )
					Map.mapDrawer.MapMeshDirty(Position, MapMeshFlag.Things);
			}

			if( def.plant.LimitedLifespan )
			{
				//Age
				ageInt += GenTicks.TickLongInterval;

				//Dying
				if( Dying)
				{
					var map = Map;
					bool isCrop = IsCrop; // before applying damage!

					int dyingDamAmount = Mathf.CeilToInt(DyingDamagePerTick * GenTicks.TickLongInterval);
					TakeDamage(new DamageInfo(DamageDefOf.Rotting, dyingDamAmount));
	
					if( Destroyed )
					{
						if( isCrop && def.plant.Harvestable && MessagesRepeatAvoider.MessageShowAllowed("MessagePlantDiedOfRot-" + def.defName, 240f) )
							Messages.Message( "MessagePlantDiedOfRot".Translate(Label).CapitalizeFirst(), new TargetInfo(Position, map), MessageSound.Negative );

						return;
					}
				}
			}
		
			//Reproduce
			if( def.plant.reproduces && growthInt >= SeedShootMinGrowthPercent )
			{
				if( Rand.MTBEventOccurs( def.plant.reproduceMtbDays, GenDate.TicksPerDay, GenTicks.TickLongInterval ) )
				{
					if( !GenPlant.SnowAllowsPlanting(Position, Map) )
						return;

					GenPlantReproduction.TryReproduceFrom(Position, def, SeedTargFindMode.Reproduce, Map);		
				}
			}

		}

		//State has changed, label may have to as well
		//Also, we want to keep this null so we don't have useless data sitting there a long time in plants that never get looked at
		cachedLabelMouseover = null;
	}

	protected virtual bool CurrentlyCultivated()
	{
		if( !def.plant.Sowable )
			return false;

		if( !Spawned )
			return false;
		
		var z = Map.zoneManager.ZoneAt(Position);
		if (z != null && z is Zone_Growing)
			return true;

		var ed = Position.GetEdifice(Map);
		if( ed != null && ed.def.building.SupportsPlants )
			return true;

		return false;
	}

	public virtual int YieldNow()
	{
		if( !HarvestableNow )
			return 0;

		//If yield is 0, handle it
		if( def.plant.harvestYield <= 0 )
			return 0;

		//Start with max yield
		float yieldFloat = def.plant.harvestYield;

		//Factor for growth
		float growthFactor = Mathf.InverseLerp( def.plant.harvestMinGrowth, 1, growthInt);
		growthFactor = 0.5f + growthFactor * 0.5f;	//Scalebias it to 0.5..1 range.
		yieldFloat *= growthFactor;

		//Factor down for health with a 50% lerp factor
		yieldFloat *=  Mathf.Lerp( 0.5f, 1f,  ((float)HitPoints / (float)MaxHitPoints) );

		//Factor for difficulty
		yieldFloat *= Find.Storyteller.difficulty.cropYieldFactor;

		return GenMath.RoundRandom(yieldFloat);		
	}

	public override void Print( SectionLayer layer )
	{
		Vector3 trueCenter = this.TrueCenter();

		Rand.PushSeed();
		Rand.Seed = Position.GetHashCode();//So our random generator makes the same numbers every time

		//Determine random local position variance
		float positionVariance;
		if( def.plant.maxMeshCount == 1 )
			positionVariance = 0.05f;
		else
			positionVariance = 0.50f;

		//Determine how many meshes to print
		int meshCount = Mathf.CeilToInt( growthInt * def.plant.maxMeshCount );
		if( meshCount < 1 )
			meshCount = 1;

		//Grid width is the square root of max mesh count
		int gridWidth = 1;
		switch( def.plant.maxMeshCount )
		{
			case 1: gridWidth = 1; break;
			case 4: gridWidth = 2; break;
			case 9: gridWidth = 3; break;
			case 16: gridWidth = 4; break;
			case 25: gridWidth = 5; break;
			default: Log.Error(def + " must have plant.MaxMeshCount that is a perfect square."); break;
		}
		float gridSpacing = 1f/gridWidth; //This works out to give half-spacings around the edges

		//Shuffle up the position indices and place meshes at them
		//We do this to get even mesh placement by placing them roughly on a grid
		Vector3 adjustedCenter = Vector3.zero;
		Vector2 planeSize = Vector2.zero;
		int meshesYielded = 0;
		var posIndexList = PlantPosIndices.GetPositionIndices(this);
		for(int i=0; i<posIndexList.Length; i++ )
		{		
			int posIndex = posIndexList[i];

			//Determine plane size
			float size = def.plant.visualSizeRange.LerpThroughRange(growthInt);

			//Determine center position
			if( def.plant.maxMeshCount == 1 )
			{
				adjustedCenter = trueCenter + new Vector3(Rand.Range(-positionVariance, positionVariance),
															 0,
															 Rand.Range(-positionVariance, positionVariance) );

				//Clamp bottom of plant to square bottom
				//So tall plants grow upward
				float squareBottom = Mathf.Floor(trueCenter.z);
				if( (adjustedCenter.z - (size/2f)) <  squareBottom )
					adjustedCenter.z = squareBottom + (size/2f);
			}
			else
			{
				adjustedCenter = Position.ToVector3(); //unshifted
				adjustedCenter.y = def.Altitude;//Set altitude

				//Place this mesh at its randomized position on the submesh grid
				adjustedCenter.x += 0.5f * gridSpacing;
				adjustedCenter.z += 0.5f * gridSpacing;
				int xInd = posIndex / gridWidth;
				int zInd = posIndex % gridWidth;
				adjustedCenter.x += xInd * gridSpacing;
				adjustedCenter.z += zInd * gridSpacing;
				
				//Add a random offset
				float gridPosRandomness = gridSpacing * GridPosRandomnessFactor;
				adjustedCenter += new Vector3(Rand.Range(-gridPosRandomness, gridPosRandomness),
											  0,
											  Rand.Range(-gridPosRandomness, gridPosRandomness) );
			}

			//Randomize horizontal flip
			bool flipped = Rand.Value < 0.5f;		

			//Randomize material
			Material mat = Graphic.MatSingle; //Pulls a random material

			//Set wind exposure value at each vertex by setting vertex color
			workingColors[1].a = workingColors[2].a = (byte)(255 * def.plant.topWindExposure);
			workingColors[0].a = workingColors[3].a = 0;

			size *= def.graphicData.drawSize.x;	//Plants don't support non-square drawSizes
			planeSize = new Vector2( size,size );
			Printer_Plane.PrintPlane( layer, 
									  adjustedCenter, 
									  planeSize,	
									  mat, 
									  flipUv: flipped, 
									  colors:  workingColors,
									  topVerticesAltitudeBias: 0.1f ); // need to beat walls corner filler (so trees don't get cut by mountains)


			meshesYielded++;
			if( meshesYielded >= meshCount )
				break;
		}

		if( def.graphicData.shadowData != null)
		{
			//Brutal shadow positioning hack
			float shadowOffsetFactor = 0.85f;
			if( planeSize.y < 1 )
				shadowOffsetFactor = 0.6f; //for bushes
			else
				shadowOffsetFactor = 0.81f;	//for cacti

			Vector3 sunShadowLoc = adjustedCenter;
			sunShadowLoc.z -= (planeSize.y/2f) * shadowOffsetFactor;
			sunShadowLoc.y -= Altitudes.AltInc;

			Printer_Shadow.PrintShadow( layer, sunShadowLoc, def.graphicData.shadowData );
		}

		Rand.PopSeed();
	}

	public override string GetInspectString()
	{
		StringBuilder sb = new StringBuilder();

		sb.Append(base.GetInspectString());
		if( LifeStage == PlantLifeStage.Growing )
		{
			sb.AppendLine("PercentGrowth".Translate(GrowthPercentString));
			sb.AppendLine("GrowthRate".Translate() + ": " + GrowthRate.ToStringPercent());

			if( Resting )
				sb.AppendLine("PlantResting".Translate());
			
			if( !HasEnoughLightToGrow )		
				sb.AppendLine("PlantNeedsLightLevel".Translate() + ": " + def.plant.growMinGlow.ToStringPercent() );

			float tempFactor = GrowthRateFactor_Temperature;
			if( tempFactor < 0.99f )
			{
				if( tempFactor < 0.01f )
					sb.AppendLine("OutOfIdealTemperatureRangeNotGrowing".Translate());
				else
					sb.AppendLine("OutOfIdealTemperatureRange".Translate(Mathf.RoundToInt(tempFactor * 100f).ToString()));
			}
			
		}
		else if( LifeStage == PlantLifeStage.Mature )
		{
			if( def.plant.Harvestable )
				sb.AppendLine("ReadyToHarvest".Translate() );
			else
				sb.AppendLine("Mature".Translate() );
		}
		
		return sb.ToString();
	}
	
	public virtual void CropBlighted()
	{
        if( Rand.Value < 0.85f )
			Destroy();
	}
}

internal static class PlantPosIndices
{
	//Cached
	//First index - for maxMeshCount
	//Second index - which of the lists for this maxMeshCount
	//Third index - which index in this list
	private static int[][][] rootList = null;

	//Constants
	private const int ListCount = 8;

	static PlantPosIndices()
	{
		rootList = new int[PlantProperties.MaxMaxMeshCount][][];
		for( int i=0; i<PlantProperties.MaxMaxMeshCount; i++ )
		{
			rootList[i] = new int[ListCount][];
			for( int j=0; j<ListCount; j++ )
			{
				int[] newList = new int[i+1];
				for( int k=0; k<i; k++ )
				{
					newList[k] = k;
				}
				newList.Shuffle();

				rootList[i][j] = newList;
			}
		}
	}

	public static int[] GetPositionIndices( Plant p )
	{
		int mmc = p.def.plant.maxMeshCount;
		int index = (p.thingIDNumber^42348528) % ListCount;
		return rootList[mmc-1][ index ];
	}
}

}